#include <xml/Stream.h>
#include <logs/Log.h>

#include <Gluck.h>
#include <resource/Resource.h>
#include <libxml/xmlreader.h>

namespace {
const char* LOGGER = "XmlStreamLibXml";
} // anonymous namespace

namespace liba {
namespace xml {

void log_unknown_node(const Utf8String& nodeName)
{
	logs::log() << "XML: Failed while parsing node " << nodeName << "\n";
	std::string nodeNameAnsi;
	converter::convert(nodeName, &nodeNameAnsi);
	LOG_WARN(LOGGER, "Failed to parse node %s", nodeNameAnsi.c_str());
}
	
void log_unknown_attribute(const Utf8String& attrName)
{
	logs::log() << "XML: Failed while parsing attribute " << attrName << "\n";
	std::string attrNameAnsi;
	converter::convert(attrName, &attrNameAnsi);
	LOG_WARN(LOGGER, "Failed to parse attribute %s", attrNameAnsi.c_str());
}

#pragma region AutoXmlParserCtxt
class AutoXmlTextReader
	: private Nocopy
{
public:
	AutoXmlTextReader()
		: reader(0)
	{ }

	AutoXmlTextReader(xmlTextReaderPtr reader)
		: reader(reader)
	{ }

	xmlTextReaderPtr get()  { return this->reader; }
	void reset(xmlTextReaderPtr newReader)
	{
		cleanup();
		this->reader = newReader;
	}

	xmlTextReaderPtr operator->() { return reader; }
	
	~AutoXmlTextReader() { cleanup(); }
private:
	void cleanup()
	{
		if (this->reader)
			xmlFreeTextReader(reader);
	}

	xmlTextReaderPtr reader;
};
#pragma endregion

class LibxmlResourceReader
{
public:
	LibxmlResourceReader(resource::Resource* r)
		: resource(r)
	{
		this->reader.reset(
			xmlReaderForIO(&InputReadCallback, 0, this, 0, 0, XML_PARSE_RECOVER));
		
		if (!this->reader.get())
			THROW(Gluck(__FUNCTION__, "Failed to create libxml reader"));
	}

	xmlTextReaderPtr get() { return this->reader.get(); }
private:
	static int XMLCALL InputReadCallback(void * context, char * buffer, int len)
	{
		LibxmlResourceReader* pThis = reinterpret_cast<LibxmlResourceReader*>(context);
		return pThis->resource->read(buffer, len);
	}

	resource::Resource* resource;
	AutoXmlTextReader reader;
};

class StreamResourceImpl
	: public Provider
{
public:
	virtual bool parse_object(Object* obj)
	{
		if (xmlTextReaderHasAttributes(this->reader->get()))
		{
			while (xmlTextReaderMoveToNextAttribute(this->reader->get()))
			{
				Utf8String attributeName = Utf8String::FromXmlChars(xmlTextReaderName(this->reader->get()));
				Utf8String attributeValue = Utf8String::FromXmlChars(xmlTextReaderValue(this->reader->get()));
				if (!obj->on_base_attribute(this, attributeName, attributeValue))
				{
					log_unknown_attribute(attributeName);
					return false;
				}
			}
			xmlTextReaderMoveToElement(this->reader->get());
		}

		if (xmlTextReaderNodeType(this->reader->get()) == XML_READER_TYPE_ELEMENT && 
			xmlTextReaderIsEmptyElement(this->reader->get()))
		{
			if (!obj->on_end_node(this))
				return false;
			return true;
		}

		int readResult;
		while ((readResult = xmlTextReaderRead(this->reader->get())) == 1)
		{
			int nodeType = xmlTextReaderNodeType(this->reader->get());
			switch (nodeType)
			{
			case XML_READER_TYPE_ELEMENT:
				{
					Utf8String nodeName = Utf8String::FromXmlChars(xmlTextReaderName(this->reader->get()));
					if (!obj->on_base_node(this, nodeName))
					{
						log_unknown_node(nodeName);
						return false;
					}
					break;
				}
			case XML_READER_TYPE_END_ELEMENT:
				{
					if (!obj->on_end_node(this))
						return false;
					return true;
				}
			}
		}

		if (readResult < 0)
		{
			xmlErrorPtr e = xmlGetLastError();
			std::string errorMessage = "Libxml2 refused to parse document: ";
			errorMessage += e->message;
			THROW(Gluck(__FUNCTION__, errorMessage));
		}

		return true;
	}
public:
	StreamResourceImpl(resource::Resource* r)
		: resource(r)
	{
	}

	bool load(Object* obj)
	{
		if (!this->resource)
			return false;

		this->reader.reset(new LibxmlResourceReader(this->resource));
		//if (!xmlTextReaderIsValid(this->reader->get()))
//			return false;

		return this->parse_object(obj);

		return false;
	}
private:
	resource::Resource* resource;
	std::auto_ptr<LibxmlResourceReader> reader;
};

StreamResource::StreamResource(resource::Resource * res)
	: impl(new StreamResourceImpl(res))
{
}

StreamResource::~StreamResource()
{
}

bool StreamResource::load(Object * obj)
{
	return impl->load(obj);
}

} // namespace xml
} // namespace liba